const OP = Object.prototype;

class Jsonob{
    constructor(obj, callback){
        if(OP.toString.call(obj) !== '[object Object]'){
            console.error('This parameter must be an object：' + obj);
        }
        this.$callback = callback;
        this.observe(obj);
    }

    observe(obj, path){
        if(OP.toString.call(obj) === '[object Array]'){
            this.proxyArr(obj, path);
        }        
        Object.keys(obj).forEach(function(key){
            var oldVal = obj[key];
            var pathArray = path && path.slice(0);
            if (pathArray) {
                pathArray.push(key)
            } else {
                pathArray = [key]
            }
            Object.defineProperty(obj, key, {
                get: function(){
                    return oldVal;
                },
                set: (function(newVal){
                    if (oldVal !== newVal) {
                        if (OP.toString.call(newVal) === '[object Object]' || OP.toString.call(newVal) === '[object Array]') {
                            this.observe(newVal, pathArray);
                        }
                        this.$callback(newVal, oldVal, pathArray);
                        oldVal = newVal                        
                    }                
                }).bind(this)
            });
            if(OP.toString.call(obj[key]) === '[object Object]' || OP.toString.call(obj[key]) === '[object Array]' ){
                this.observe(obj[key], pathArray);
            }
        }, this);
    }

    proxyArr (arr, path) {
        var self = this
        const OAM = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
        var overrideProto = Object.create(Array.prototype)
        OAM.forEach(item => {
            var oldArray = [];
            overrideProto[item] = function () {
                oldArray = this.slice(0);
                console.log(`${item}: trigger`)
                var arg = [].slice.apply(arguments)
                var result = Array.prototype[item].apply(this, arg)
                // 对新的数组进行监测
                self.observe(this, path);
                // 执行回调
                self.$callback(this, oldArray, path);                
                return result   
            }
        })
        arr.__proto__ = overrideProto
        return arr
    }    
}

// // 定义一个普通对象作为数据模型
var data = {
    a: 200,
    level1: {
        b: 'str',
        c: [1, 2, 3],
        level2: {
            d: 90
        }
    }
}

// // 定义一个变化通知的回调
var callback = function(newVal, oldVal, path){
    console.log('新值：' + newVal + '----' + '旧值：' + oldVal + '----路径：' + path);
};

// // 实例化一个监测对象，去监测数据，并在数据发生改变的时候作出反应
var j = new Jsonob(data, callback);

data.a = 999
data.level1.b = 'test'
data.level1.level2.d = 1212
data.level1.c.push(4)